#include "common.h"
#include "device.h"

#ifdef _WIN32
#   define NOMINMAX
#   define WIN32_LEAN_AND_MEAN
#   include <Windows.h>
#   include <deque>
#else
#   include <sys/ioctl.h>
#   include <sys/mount.h>
#   include <fcntl.h>
#endif

namespace cpfs {

#ifdef _WIN32
    static std::string strerror_win32(DWORD lastError = ::GetLastError()) {
        char *s = NULL;
        verify(0 != FormatMessage(
            FORMAT_MESSAGE_ALLOCATE_BUFFER |
            FORMAT_MESSAGE_FROM_SYSTEM |
            FORMAT_MESSAGE_IGNORE_INSERTS,
            NULL,
            lastError,
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
            (LPTSTR) &s,
            0,
            NULL));
        std::string retval(s);
        LocalFree(s);
        return retval;
    }
#endif

struct DeviceImpl {
    #ifdef _WIN32
        HANDLE handle;
        std::deque<OVERLAPPED> overlaps;
    #else
        int fd;
    #endif
};

Device::Device(char const *path, Offset size, Offset off)
    :   impl_(new DeviceImpl())
{
#ifdef _WIN32
    if (INVALID_HANDLE_VALUE == (impl_->handle = CreateFile(
            path,
            GENERIC_READ|GENERIC_WRITE,
            FILE_SHARE_WRITE,
            NULL,
            OPEN_EXISTING,
            FILE_FLAG_OVERLAPPED,
            NULL))) {
        throw Win32Error("Error opening device");
    }
#else
    // O_BINARY is not on Linux
    if (-1 == (impl_->fd = ::open(path, O_RDWR|O_NOATIME))) {
        throw SystemError("Error opening device");
    }
#endif
    off_ = off == -1 ? 0 : off;
    size_ = size;
    if (size_ == -1) {
    #ifdef _WIN32
        LARGE_INTEGER newpos;
        if (!SetFilePointerEx(impl_->handle, convert(0), &newpos, FILE_END))
            throw_exception_win32();
        size_ = newpos.QuadPart - off_;
    #else
        size_ = lseek64(impl_->fd, 0, SEEK_END);
        if (size_ == -1) throw SystemError("Error getting device size");
    #endif
    }
}
Device::~Device() {
    #ifdef _WIN32
        CloseHandle(impl_->handle);
    #else
        ::close(impl_->fd);
    #endif
}
CpfsOff Device::size() const {
    assert(size_ != -1);
    return size_;
}

void Device::write(void const *buf, size_t count, Offset off)
{
    #ifdef _WIN32
        OVERLAPPED overlap = {};
        LARGE_INTEGER largeInt;
        largeInt.QuadPart = off_ + off;
        overlap.Offset = largeInt.LowPart;
        overlap.OffsetHigh = largeInt.HighPart;
        overlap.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
        impl_->overlaps.push_back(overlap);
        verify(!WriteFile(impl_->handle, buf, count, NULL, &impl_->overlaps.back()));
        verify(GetLastError() == ERROR_IO_PENDING);
    #else
        if ((ssize_t)count != pwrite(impl_->fd, buf, count, off)) {
            throw SystemError("Error writing to device");
        }
    #endif
}

void Device::read(void *buf, size_t count, Offset off) const
{
#   ifdef _WIN32
        OVERLAPPED overlap = {0};
        LARGE_INTEGER li;
        li.QuadPart = off_ + off;
        overlap.Offset = li.LowPart;
        overlap.OffsetHigh = li.HighPart;
        verify(INVALID_HANDLE_VALUE != (overlap.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL)));
        verify(!ReadFile(impl_->handle, buf, count, NULL, &overlap));
        verify(GetLastError() == ERROR_IO_PENDING);
        DWORD bytesRead;
        verify(GetOverlappedResult(impl_->handle, &overlap, &bytesRead, TRUE));
        assert(bytesRead == count);
        verify(CloseHandle(overlap.hEvent));
#   else
        if ((ssize_t)count != pread(impl_->fd, buf, count, off)) {
            throw SystemError("Error reading from device");
        }
#   endif
}

void Device::flush()
{
    #ifdef _WIN32
        while (!impl_->overlaps.empty()) {
            OVERLAPPED &overlap(impl_->overlaps.front());
            DWORD bytesWritten;
            verify(GetOverlappedResult(impl_->handle, &overlap, &bytesWritten, TRUE));
            assert(bytesWritten == g_cpfs->geo.block_size);
            verify(CloseHandle(overlap.hEvent));
            impl_->overlaps.pop_front();
        }
    #else
        if (0 != fdatasync(impl_->fd)) throw SystemError("Error flushing to device");
    #endif
}

} // namespace cpfs {
